﻿using System;
using System.Net;
using System.Net.Sockets;

namespace PacketRush.Core
{
    /// <summary>
    /// A server-side networking provider that uses UDP for reliable and unreliable communication.
    /// </summary>
    /// <remarks>
    /// TODO: Implement connection limitation with ServerSettings.MaxConnections.
    /// </remarks>
    public class ReliableUdpServerNetworkingProvider : ReliableUdpNetworkingProvider, IServerNetworkingProvider
    {
        /// <summary>
        /// Occurs during an <see cref="PacketRush.Core.ReliableUdpServerNetworkingProvider.Update"/>, raised for each
        /// connection before any logic is performed on the connection.
        /// </summary>
        public event EventHandler<ReliableUdpEventArgs> BeforeUpdate;

        /// <summary>
        /// Raises the <see cref="PacketRush.Core.ReliableUdpServerNetworkingProvider.BeforeUpdate"/> event.
        /// </summary>
        protected void OnBeforeUpdate(ReliableUdpConnection conn)
        {
            if (BeforeUpdate != null)
            {
                BeforeUpdate(this, new ReliableUdpEventArgs(conn));
            }
        }

        private NetConnectionCollection<ReliableUdpConnection> _Connections = new NetConnectionCollection<ReliableUdpConnection>(16, true, true);
        private NetConnectionCollection<ReliableUdpConnection> _NewConnections = new NetConnectionCollection<ReliableUdpConnection>(16, false, false);

        private ReliableUdpServerSettings _Settings;

        /// <summary>
        /// Settings for the server.
        /// </summary>
        public NetServerSettings ServerSettings { get; set; }

        /// <summary>
        /// Time measured from when <see cref="PacketRush.Core.ReliableUdpServerNetworkingProvider.Start"/> was called.
        /// </summary>
        /// <remarks>
        /// Time measured from when <see cref="PacketRush.Core.ReliableUdpServerNetworkingProvider.Start"/> was called, resets to 
        /// zero on stop.
        /// </remarks>
        public override NetTime RunningTime { get; set; }

        /// <summary>
        /// Create an instance with default settings.
        /// </summary>
        public ReliableUdpServerNetworkingProvider() : this(new ReliableUdpServerSettings())
        {

        }

        /// <summary>
        /// Create an instance with specific settings.
        /// </summary>
        /// <param name="settings">Settings to create an instance with.</param>
        public ReliableUdpServerNetworkingProvider(ReliableUdpServerSettings settings) : base(settings)
        {
            _Settings = settings;
        }

        /// <summary>
        /// Start the networking provider.
        /// </summary>
        /// <param name="localEndPoint">The local end point.</param>
        public void Start(EndPoint localEndPoint)
        {
            Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            Socket.Bind(localEndPoint);
        }
        /// <summary>
        /// Accept pending connections.
        /// new connections.
        /// </summary>
        /// <returns>An array of INetConnection.</returns>
        public INetConnection[] AcceptConnections()
        {
            int count = _NewConnections.Count;
            INetConnection[] connections = new INetConnection[count];
            for (int i = 0; i < count; i++)
            {
                ReliableUdpConnection connection = _NewConnections[i];
                _Connections.Add(connection);
                connections[i] = connection;
            }

            _NewConnections.Clear();

            return connections;
        }
        /// <summary>
        /// Disconnect this provider from the network.
        /// </summary>
        public void Disconnect(INetConnection connection)
        {
            ReliableUdpConnection conn = _Connections.GetByRemoteEndPoint(connection.RemoteEndPoint);
            if (conn != null) _Connections.Remove(conn);
        }

        /// <summary>
        /// Called during a <see cref="PacketRush.Core.ReliableUdpNetworkingProvider.Receive"/> when a packet is received.
        /// </summary>
        /// <param name="rawPacket">The packet received.</param>
        /// <param name="serviceId">The service id of the service raw packet is destined for.</param>
        /// <param name="connection">The connection the packet was received from.</param>
        protected override void ProcessReceivedPacket(byte[] rawPacket, int serviceId, ReliableUdpConnection connection)
        {
            // This is a new connection, add it to the new connections queue
            if (serviceId == ReliableUdpSystemPacketTypes.ConnectionRequest)
            {
                // Connect this connection
                connection.Connected = true;
                connection.AssignNewId();
                _NewConnections.Add(connection);
                return;
            }

            // This is a disconn
            if (serviceId == ReliableUdpSystemPacketTypes.DisconnectionNotification)
            {
                // Disconnect this connection
                connection.Connected = false;
                return;
            }

            // only allow connected clients to pass up packets
            if (connection.Connected) ReceivePacketAction(rawPacket, connection);
        }

        /// <summary>
        /// Performs additional networking logic.
        /// </summary>
        /// <remarks>
        /// <c>Update</c> enumerates packets waiting for acknowledgement for each client. For each packet it checks
        /// if the time the packet was sent is greater than the <see cref="PacketRush.Core.ReliableUdpSettings.PacketResendRate"/>,
        /// if it is then the packet is resent.
        /// </remarks>
        public override void Update()
        {
            long now = RunningTime.ElapsedMilliseconds;

            // resend packets that are waiting for acks
            for (int i = 0; i < _Connections.Count; i++)
            {
                ReliableUdpConnection connection = _Connections[i];

                OnBeforeUpdate(connection);

                foreach (uint key in connection.AckWaitingList.Keys)
                {
                    AckWaitingPacket packet = connection.AckWaitingList[key];

                    if ((now - packet.TimeSent) > _Settings.PacketResendRate)
                    {
                        // Resend this packet
                        Socket.SendTo(packet.RawPacket, connection.RemoteEndPoint);
                        packet.TimeSent = now;
                        packet.Tries++;

                        // If we have reached our limit then disconnect this connection
                        if (packet.Tries > _Settings.MaxPacketResends) connection.Connected = false;
                    }
                }
            }
        }

        /// <summary>
        /// Used by receive to lookup a connection by endpoint.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Used during a <see cref="PacketRush.Core.ReliableUdpNetworkingProvider.Receive"/> to find a connection
        /// by the given end point.
        /// </para>
        /// <para>
        /// Uses the service id to determine if the packet is a connection request packet.
        /// </para>
        /// </remarks>
        /// <param name="endPoint">The endpoint to lookup the connection by.</param>
        /// <param name="serviceId">The service id of the packet received.</param>
        /// <returns>A connection.</returns>
        protected override ReliableUdpConnection LookupConnection(EndPoint endPoint, int serviceId)
        {
            ReliableUdpConnection connection = _Connections.GetByRemoteEndPoint(endPoint);

            // Ok, not in the connections list, are they in the new connections list?
            if (connection == null) connection = _NewConnections.GetByRemoteEndPoint(endPoint);

            // Are we still feckin null?
            if (connection == null)
            {
                // If we are requesting a connection then return a new connection
                if (serviceId == ReliableUdpSystemPacketTypes.ConnectionRequest)
                {
                    return new ReliableUdpConnection(endPoint, Socket.LocalEndPoint);
                }
            }

            return connection;
        }

        /// <summary>
        /// Disconnect this provider from the network.
        /// </summary>
        public override void Disconnect()
        {
            _Connections.Clear();
            Socket.Close();
            Socket = null;
        }

        /// <summary>
        /// Called during a <see cref="PacketRush.Core.ReliableUdpNetworkingProvider.Receive"/> when an 
        /// acknowledgement packet is received.
        /// </summary>
        /// <param name="rawPacket">The packet received.</param>
        /// <param name="serviceId">The service id of the service raw packet is destined for.</param>
        /// <param name="connection">The connection the packet was received from.</param>
        protected override void ProcessReceivedAckPacket(byte[] rawPacket, int serviceId, ReliableUdpConnection connection)
        {

        }
    }
}
